#
# 计算报表名称和数据项相似度建立映射表
import pandas as pd
import numpy as np
from rapidfuzz import process, fuzz
import re
from collections import defaultdict
import argparse
import os
from openpyxl import load_workbook
import warnings

warnings.filterwarnings('ignore')


class DataItemStandardizer:
    def __init__(self, file_path, output_path,sheet_name, similarity_threshold=80):
        """
        初始化数据标准化器

        参数:
            file_path: Excel文件路径
            sheet_name: 要读取的工作表名称
            similarity_threshold: 相似度阈值(0-100)，用于判断是否为同义词
        """
        self.file_path = file_path
        self.output_path = output_path  #添加
        self.sheet_name = sheet_name
        self.similarity_threshold = similarity_threshold
        self.df = None
        self.report_mappings = {}  # 报表名称映射表
        self.data_item_mappings = {}  # 数据项映射表
        self.load_data()

    def load_data(self):
        """加载Excel数据"""
        try:
            # 读取Excel文件的指定sheet
            self.df = pd.read_excel(self.file_path, sheet_name=self.sheet_name)
            print(f"成功加载数据，共 {len(self.df)} 行")
            print("列名:", self.df.columns.tolist())

            # 检查必要的列是否存在
            required_columns = ['部门名称', '报表名称', '数据项']
            for col in required_columns:
                if col not in self.df.columns:
                    raise ValueError(f"缺少必要列: {col}")

        except Exception as e:
            print(f"加载数据失败: {e}")
            raise

    def preprocess_text(self, text):
        """
        文本预处理：清洗和标准化

        参数:
            text: 待处理的文本

        返回:
            处理后的文本
        """
        if pd.isna(text):
            return ""

        # 转换为字符串
        text = str(text)

        # 去除空格
        text = text.strip()

        # 转换全角字符到半角
        full_to_half_map = {
            '，': ',', '：': ':', '；': ';',
            '！': '!', '？': '?', '（': '(',
            '）': ')', '【': '[', '】': ']'
        }
        for full, half in full_to_half_map.items():
            text = text.replace(full, half)

        return text

    def apply_preprocessing(self):
        """应用数据预处理"""
        print("正在进行数据预处理...")
        self.df['部门名称_清洗'] = self.df['部门名称'].apply(self.preprocess_text)
        self.df['报表名称_清洗'] = self.df['报表名称'].apply(self.preprocess_text)
        self.df['数据项_清洗'] = self.df['数据项'].apply(self.preprocess_text)
        print("数据预处理完成")

    def generate_report_mappings(self):
        """
        按部门分组生成报表名称映射表
        """
        print("正在生成报表名称映射表...")

        # 按部门分组处理
        departments = self.df['部门名称_清洗'].unique()
        report_mappings = []

        for dept in departments:
            dept_data = self.df[self.df['部门名称_清洗'] == dept]
            reports = dept_data['报表名称_清洗'].unique()

            # 跳过空项
            reports = [report for report in reports if report and str(report).strip()]

            processed_reports = set()

            # 对每个报表名称，查找相似项
            for report in reports:
                if report in processed_reports:
                    continue

                # 查找相似项
                other_reports = [r for r in reports if r != report and r not in processed_reports]
                if not other_reports:
                    # 没有相似项，自身作为标准项
                    report_mappings.append({
                        '部门名称': dept,
                        '原始报表名称': report,
                        '标准报表名称': report
                    })
                    processed_reports.add(report)
                    continue

                # 使用rapidfuzz查找最相似的项
                matches = process.extract(
                    report, other_reports,
                    scorer=fuzz.token_sort_ratio,
                    limit=10
                )

                # 筛选高于阈值的匹配项
                similar_reports = []
                for matched_report, score, _ in matches:
                    if score >= self.similarity_threshold:
                        similar_reports.append(matched_report)
                        processed_reports.add(matched_report)

                if similar_reports:
                    # 选择最长的项作为标准项（通常更完整）
                    standard_report = report
                    for sim_report in similar_reports:
                        if len(sim_report) > len(standard_report):
                            standard_report = sim_report

                    # 记录映射关系
                    report_mappings.append({
                        '部门名称': dept,
                        '原始报表名称': report,
                        '标准报表名称': standard_report,
                        '相似度': score
                    })

                    for sim_report in similar_reports:
                        report_mappings.append({
                            '部门名称': dept,
                            '原始报表名称': sim_report,
                            '标准报表名称': standard_report,
                            '相似度': score
                        })

                    processed_reports.add(report)
                    processed_reports.update(similar_reports)

        # 转换为DataFrame
        report_mapping_df = pd.DataFrame(report_mappings)
        print(f"报表名称映射表生成完成，共 {len(report_mapping_df)} 条映射关系")

        return report_mapping_df

    def generate_data_item_mappings(self, report_mapping_df):
        """
        按部门和报表分组生成数据项映射表

        参数:
            report_mapping_df: 报表名称映射表
        """
        print("正在生成数据项映射表...")

        # 应用报表名称映射
        report_mapping_dict = {}
        for _, row in report_mapping_df.iterrows():
            key = (row['部门名称'], row['原始报表名称'])
            report_mapping_dict[key] = row['标准报表名称']

        # 添加标准报表名称列
        self.df['标准报表名称'] = self.df.apply(
            lambda row: report_mapping_dict.get(
                (row['部门名称_清洗'], row['报表名称_清洗']),
                row['报表名称_清洗']
            ),
            axis=1
        )

        # 按部门和标准报表名称分组处理数据项
        data_item_mappings = []

        # 获取所有部门和报表的组合
        dept_report_groups = self.df.groupby(['部门名称_清洗', '标准报表名称'])

        for (dept, report), group in dept_report_groups:
            items = group['数据项_清洗'].unique()

            # 跳过空项
            items = [item for item in items if item and str(item).strip()]

            processed_items = set()

            # 对每个数据项，查找相似项
            for item in items:
                if item in processed_items:
                    continue

                # 查找相似项
                other_items = [i for i in items if i != item and i not in processed_items]
                if not other_items:
                    # 没有相似项，自身作为标准项
                    data_item_mappings.append({
                        '部门名称': dept,
                        '报表名称': report,
                        '原始数据项': item,
                        '标准数据项': item
                    })
                    processed_items.add(item)
                    continue

                # 使用rapidfuzz查找最相似的项
                matches = process.extract(
                    item, other_items,
                    scorer=fuzz.token_sort_ratio,
                    limit=10
                )

                # 筛选高于阈值的匹配项
                similar_items = []
                for matched_item, score, _ in matches:
                    if score >= self.similarity_threshold:
                        similar_items.append(matched_item)
                        processed_items.add(matched_item)

                if similar_items:
                    # 选择最长的项作为标准项（通常更完整）
                    standard_item = item
                    for sim_item in similar_items:
                        if len(sim_item) > len(standard_item):
                            standard_item = sim_item

                    # 记录映射关系
                    data_item_mappings.append({
                        '部门名称': dept,
                        '报表名称': report,
                        '原始数据项': item,
                        '标准数据项': standard_item,
                        '相似度':score
                    })

                    for sim_item in similar_items:
                        data_item_mappings.append({
                            '部门名称': dept,
                            '报表名称': report,
                            '原始数据项': sim_item,
                            '标准数据项': standard_item,
                            '相似度': score
                        })

                    processed_items.add(item)
                    processed_items.update(similar_items)

        # 转换为DataFrame
        data_item_mapping_df = pd.DataFrame(data_item_mappings)
        print(f"数据项映射表生成完成，共 {len(data_item_mapping_df)} 条映射关系")

        return data_item_mapping_df

    def apply_standardization(self, data_item_mapping_df):
        """
        应用标准化映射

        参数:
            data_item_mapping_df: 数据项映射表

        返回:
            标准化后的DataFrame
        """
        print("正在应用标准化映射...")

        # 创建映射字典
        mapping_dict = {}
        for _, row in data_item_mapping_df.iterrows():
            key = (row['部门名称'], row['报表名称'], row['原始数据项'])
            mapping_dict[key] = row['标准数据项']

        # 应用映射
        self.df['数据项_标准'] = self.df.apply(
            lambda row: mapping_dict.get(
                (row['部门名称_清洗'], row['标准报表名称'], row['数据项_清洗']),
                row['数据项_清洗']
            ),
            axis=1
        )

        print("标准化映射应用完成")
        return self.df

    def save_results(self):
        """
        保存结果到原Excel文件的不同sheet中
        """
        print("正在保存结果到Excel文件...")

        # 生成报表名称映射表
        report_mapping_df = self.generate_report_mappings()

        # 生成数据项映射表
        data_item_mapping_df = self.generate_data_item_mappings(report_mapping_df)

        # 应用标准化
        self.apply_standardization(data_item_mapping_df)

        # 加载原Excel文件
        book = load_workbook(self.file_path)

        # 创建Excel写入器 - 使用追加模式
        with pd.ExcelWriter(self.output_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer:
            # 保存原始数据（带标准化列）
            self.df.to_excel(writer, sheet_name='标准化数据', index=False)

            # 保存报表名称映射表
            report_mapping_df.to_excel(writer, sheet_name='报表名称映射表', index=False)

            # 保存数据项映射表
            data_item_mapping_df.to_excel(writer, sheet_name='数据项映射表', index=False)

            # 保存标准化后的唯一数据项列表（按部门和报表分组）
            unique_items = []
            for (dept, report), group in self.df.groupby(['部门名称_清洗', '标准报表名称']):
                items = group['数据项_标准'].unique()
                for item in items:
                    unique_items.append({
                        '部门名称': dept,
                        '报表名称': report,
                        '标准数据项': item
                    })

            unique_df = pd.DataFrame(unique_items)
            unique_df.to_excel(writer, sheet_name='唯一标准数据项', index=False)

        print(f"结果已保存到原文件: {self.output_path}")
        return self.output_path


def main():
    file_path = "C:/Users/xingwenzheng/Desktop/国家部委-分割后基层报表数据项.xlsx"
    output_path = "C:/Users/xingwenzheng/Desktop/同义词映射表.xlsx"
    # 设置命令行参数
    parser = argparse.ArgumentParser(description='数据项标准化工具')
    parser.add_argument('--file_path', default=file_path)
    parser.add_argument('--output_path', default=output_path)
    parser.add_argument('--sheet', default='分割后数据', help='要读取的工作表名称，默认为Sheet1')
    parser.add_argument('--threshold', type=int, default=80,
                        help='相似度阈值(0-100)，默认80')

    args = parser.parse_args()

    # 初始化标准化器
    standardizer = DataItemStandardizer(args.file_path,args.output_path, args.sheet, args.threshold)

    # 应用预处理
    standardizer.apply_preprocessing()

    # 保存结果
    output_path = standardizer.save_results()

    print("\n处理完成!")
    print(f"原始数据项数量: {len(standardizer.df)}")
    print(f"唯一原始数据项数量: {standardizer.df['数据项_清洗'].nunique()}")
    print(f"唯一标准数据项数量: {standardizer.df['数据项_标准'].nunique()}")
    print(f"结果已保存到原文件: {output_path}")


if __name__ == "__main__":
    main()
